/*!
 * \file common.hpp
 */

//      Redistribution and use in source and binary forms, with or without
//      modification, are permitted provided that the following conditions are
//      met:
//
//      * Redistributions of source code must retain the above copyright
//        notice, this list of conditions and the following disclaimer.
//      * Redistributions in binary form must reproduce the above
//        copyright notice, this list of conditions and the following disclaimer
//        in the documentation and/or other materials provided with the
//        distribution.
//      * Neither the name of the  nor the names of its
//        contributors may be used to endorse or promote products derived from
//        this software without specific prior written permission.
//
//      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//      A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
//      OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
//      SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
//      LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//      DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
//      THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//      (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//      OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


#ifndef         __COMMON__
    #define     __COMMON__
    #include    <allegro.h>
    #include    <GL/gl.h>       // not supposed to have to do this, but g++ kept whining...
    #include    <alleggl.h>
    #include    <typeinfo>
    #include    <cmath>
    #include    <cstdio>
    #include    <cstdlib>
    #include    <cstddef>
    #include    <cstdarg>

    /*! @{
     * \brief These are here to ease porting to OpenGL ES in the future.
     */
    #define     glFrustumf      glFrustum
    #define     glOrthof        glOrtho
    /*! @} */

    /*!
     * \brief Quick macro to aid in debugging - will print the current file,
     * line, function and a user-supplied string.
     */
    #define     DUH_WHERE_AM_I(...) {                                             \
                                        fprintf(stderr,"\n%s, %s(), line %i: ",   \
                                        __FILE__, __FUNCTION__, __LINE__);        \
                                        fprintf(stderr, __VA_ARGS__);             \
                                    }

    /*! \brief Convert from degrees (which OpenGL likes) to radians (which libm likes). */
    #define     TO_RADIANS(theta)   (((theta * M_PI) / 180.0))
    /*! \brief Convert from radians (which libm likes) to degrees (which OpenGL likes). */
    #define     TO_DEGREES(theta)   ((theta * (180.0 / M_PI)))

    /**************************************************************************************/
    /*!
     * \brief Represents a point in 3D space, used to make composing arrays of vertices
     * to be drawn later easier.
     */
    typedef struct
    {
        GLfloat x;
        GLfloat y;
        GLfloat z;
    }
    VERTEX;

    /**************************************************************************************/
    /*!
     * \brief Represents a texture coordinate, used to make composing arrays of vertices
     * to be drawn later easier.
     */
    typedef struct
    {
        GLfloat u;
        GLfloat v;
    }
    TEX_COORD;

    /**************************************************************************************/
    /*!
     * \brief Wraps up most of the application's state and things every part of the
     * application needs access to.
     *
     * \note TODO: make this be a proper singleton at some point (maybe, not sure if
     * that buys us anything extra in terms of clarity or maintainability at this point).
     */
    class APP
    {
        public:
            static const int    HOME_PATH_LENGTH    =   4096;
            static const int    FAR_CLIP_DEPTH      =   4096;
            static const int    DISPLAY_WIDTH       =   800;
            static const int    DISPLAY_HEIGHT      =   600;
            static const int    UPDATES_PER_SEC     =   60;
            static void         setup(bool fullscreen);
            static void         shutdown();
            static int          get_num_joysticks();
            static float        get_effective_width();
            static GLuint       load_texture(const char *filename);
            static void         unload_texture(GLuint *tex);
            static void         draw_sprite(GLuint sprite_tex, float x, float  y, float  z, float size);
            static void         draw_sprite(GLuint sprite_tex, float x, float  y, float  z, float w, float h);
            static void         gfx_printf(GLuint font_tex, GLfloat x, GLfloat y, GLfloat z, GLfloat size, GLfloat spacing, const char *str, ...);
            static void         look_at(GLfloat cam_x, GLfloat cam_y, GLfloat cam_z, GLfloat dest_x, GLfloat dest_y, GLfloat dest_z);
            static bool         in_perspective_mode();
            static GLuint        get_game_font();
            static void         repaint();
            static void         logic_done();
            static void         to_ortho();
            static void         to_perspective();
            static void         show_fps();
            static bool         is_time_for_logic();

        private:
            static float        aspect_ratio;
            static float        effective_width;
            static bool         perspective_mode;
            static GLuint        game_font;
            static float        x_rot;
            static float        y_rot;
            static char         user_home_path[HOME_PATH_LENGTH];

            static volatile int     frames_per_sec;
            static volatile int     frames_this_sec;
            static volatile bool    time_for_logic;
            static void             heartbeat();
            static void             sample_framerate();
    };

    /**************************************************************************************/
    /*!
     * \brief Wraps keyboard and joystick input into a format better-suited to this game.
     */
    class INPUT
    {
        public:
            static const int DEAD_ZONE = 64; //!< For when we need the analogue nub to emulate buttons (namely menus)
            enum            BUTTON_STATE {BTN_IDLE, BTN_PRESSED, BTN_HELD};
            static void     poll();
            BUTTON_STATE    get_fire_ok_state();
            BUTTON_STATE    get_thrust_state();
            BUTTON_STATE    get_roll_cancel_state();
            BUTTON_STATE    get_up_state();
            BUTTON_STATE    get_down_state();
            float           get_analogue_x();
            float           get_analogue_y();
            float           get_analogue_angle();
            float           get_analogue_distance();
            INPUT(int which);

        private:
            int             which;
            BUTTON_STATE    fire_ok;
            BUTTON_STATE    thrust;
            BUTTON_STATE    roll_cancel;
            BUTTON_STATE    up;
            BUTTON_STATE    down;
    };

    /**************************************************************************************/
    /*!
     * \brief Represents a state the application can be in, like the main menu, gameplay,
     * the high score screen, etc.
     */
    class GAME_STATE
    {
        public:
            virtual void load(void);
            virtual void unload(void);
            virtual void update(void);
            virtual void paint(void);
            virtual void init(void);
    };

    /**************************************************************************************/
    /*!
     * \brief Holds and can draw any of a group of frames.
     */
    class ANIMATION
    {
        private:
            unsigned char   num_frames;
            unsigned char   num_ticks_to_hold_frame;
            GLuint            texture;
        public:
            enum    BLEND_MODE {ADDITIVE, SUBTRACTIVE, ALPHA};
            ANIMATION(const char *image_filename, int num_frames);
            ~ANIMATION();
            void paint(float x, float y, float z, float size, float angle, unsigned char frame);
            void paint(float x, float y, float z, float size, float angle, unsigned char frame,
                BLEND_MODE blend_mode);
    };

    /**************************************************************************************/
    /*!
     * \brief Represents an interactable thing in the gameworld, such as a powerup, a
     * bullet, the player's ship, etc.  It's abstract - child classes should add
     * functionality specific to their kinds as well as override the hit handler.
     */
    class ENTITY
    {
        protected:
            // position and movement
            float            x;
            float            y;
            float            x_speed;
            float            y_speed;
            float            angle;

            // logic and animation
            unsigned char    state;
            unsigned char    logic_clock;
            unsigned char    curr_anim_frame;
            unsigned char    curr_anim_tick;
            // ANIMATION        &anim;
            float            mass;
            float           radius;

            virtual void on_hit(ENTITY &impactor) = 0;

        public:
            virtual void update(void)   = 0;
            virtual void paint(void)    = 0;
            static bool check_collision(ENTITY &a, ENTITY &b);
    };

    /**************************************************************************************/
    /*!
     * \brief Weapon fire that travels until it hits an enemy or a rock.
     */
    class BULLET : public ENTITY
    {
        public:
            enum    FIRED_BY {PLYR1, PLYR2, PLYR3, PLYR4, SAUCER};
            static void fire(float x, float y, float angle, FIRED_BY whom);
            static void update_all();
            static void init();
            static void cleanup();
            static void reset_pool();

            BULLET() {};
            void update(void);
            void paint(void);

        private:
            void on_hit(ENTITY &impactor);
            static const int NUM_BULLETS = 512;
            static BULLET       pool[NUM_BULLETS];
            static int          pool_index;
            static ANIMATION    *anim;
            static FIRED_BY     fired_by;
    };

    /**************************************************************************************/
    /*!
     * \brief Big, heavy and annoying rocks that bounce around and try to kill the player.
     */
    class ASTEROID : public ENTITY
    {
        friend class BULLET;
        private:
            void on_hit(ENTITY &impactor);
            float rotate_speed;
    };

    /**************************************************************************************/
    /*!
     * \brief One of the players' ships.
     */
    class PLAYER_SHIP : public ENTITY
    {
        friend class BULLET;
        public:
            void handle_input(INPUT &input);
        private:
            void on_hit(ENTITY &impactor);
    };

    /**************************************************************************************/
    /*!
     * \brief One of the players' ships.
     */
    class POWERUP : public ENTITY
    {
        friend class PLAYER_SHIP;
        public:
            enum    POWERUP_TYPE {EXTRA_SHIELD, SCREWY_CONTROLS, INVINCIBILITY};
            void    spawn(float x, float y, POWERUP_TYPE which);
        private:
            void on_hit(ENTITY &impactor);
    };


#endif // __COMMON__
